虚拟试衣小程序项目总结
RT,小程序上的虚拟试衣项目已经上线啦,消失了一个月都是在忙前忙后这一个项目,把项目中遇见的一些小程序的坑、wepy 框架的坑都记录一下。
项目简介
虚拟试衣小程序项目是一个微信环境内的电商平台,与普通电商项目技术方面的最大区别在于虚拟形象的实现,虚拟形象的实现依赖于 Canvas 的绘制,在预研的时候就发现了小程序提供的 Canvas 缺少 API - canvasContext.globalCompositeOperation
。
在后面我们开发团队有向小程序技术团队提出过补充 globalCompositeOperation
的需求,并且小程序技术团队很快提供了该 API,但是该 API 的实现仍有一定的不完整,如下图:
还是要为小程序技术团队打 Call, 他们的响应和回馈都是非常迅速。
除了 API 不完整的限制,小程序的 Canvas 在 Scroll-View 和 Swiper-View 两种场景下都可能引发性能问题,所以官方也不推荐在这种场景下使用 Canvas。因此整个项目采用 H5 + Native 的架构实现。以下就会记录在这一开发过程中遇见的问题:
基础架构
项目是使用 wepy + redux 的框架实现,在解决通用数据的存储与访问的问题上,redux 给予了很好的帮助。但是除了数据,每个页面都会有一部分重复的逻辑需要实现,列如:用户登录校验、手机号绑定校验、获取购物车数据与刷新等等行为。
这里我采用了基类的实现方式,如下:
|
|
然后让后续的页面通过继承该基类,省去许多重复性工作,比如用户的数据、购物车数据、登录的校验等等,小程序页面实现如下:
|
|
这里可以看到我有许多合并操作,这里是为了让在 basePage 基类中注入的 redux 的 state 和 actions 能够在小程序页面中正常访问而做的。
如果希望在 onLoad 的钩子函数里面做更多页面的逻辑,需要调用 super.onLoad()
, 确保基类中的钩子函数能够正常触发,但是没有什么好办法来解决异步的问题,比如页面中某个逻辑依赖 user 的数据,但是我无法通过 promise 来保证代码的同步执行,所以这里只能考虑用 watch 来实现。
然后在可以通过在 data 中定义一些变量来控制基类中的执行逻辑,比如当前页面其实不需要加载购物车数据,那么我们在 data 中定义 notNeedLoadBox
来控制基类的行为。
这里我们考虑不采用基类的方式也是可以的,通过 mixins 也能够达到同样的逻辑,之所以用基类的方式,是因为我发现 wepy 在 redux 的 actions 上有 bug。mixins 的方式无法访问到注入的 actions。
以上就是项目的基本架构实现,整体上还是挺方便的,可以减少许多重复的逻辑和代码。
webview
如之前所说,因为虚拟试衣形象的问题,我们必须将一部分代码放到 H5 上面来实现。所以整个项目的代码其实是分为两部分的,H5 上面的代码是放到另一个项目上面的,框架和架构都是正常的 vue spa 项目。
spa 路由的问题
这里 vue-router 其实是没有问题的,问题出在小程序的回退按钮上面:
如果用户打开小程序看到的第一个页面是 webview 实现的 H5 页面,那么接下来在 H5 内的 pushState 行为都不会产生回退按钮。如下图:
当前是首页的商品列表页面,左上角是没有回退按钮的,此时我浏览列表后进入一个商品的详情页面,也是 H5 方式实现,对于我而言就是一次 H5 的 pushState 行为,问题就在这里,SPA 的路由跳转对于小程序而言它不是小程序路由的改变,依然不会有回退按钮的实现。(不过搞人的是,如果之前就已经有回退按钮的话,点击回退却会触发 H5 内的路由回退)。
对于这个问题我的解决办法比较呆,就是将每个 H5 页面都用一个 webiview 是装载,也就是说每次路由跳转都是走的小程序的路由跳转。
webview 再次唤起的问题
如果当前页面的 webview 触发了路由跳转到另外一个页面,在回退回来后页面是完全被缓存了的,H5 内的数据无法被更新。也就是说当 webiview 内的 H5 页面再次进入 viewport 时,vue 的任何钩子函数都不会被执行(created、mounted), 不仅仅是 vue 的钩子函数,wwindow 的 pageShow 的 API 也不会被执行,同理在 H5 页面进入后台时,(beforeDestroyed、destoryed) 也不会被执行,pageHide 也不会被执行。
这样就很坑爹了,因为在其他页面如果用户也操纵了购物车内的数据, 那么回退到 H5 页面就发现购物车数据被还原了。
这里我的解决办法是通过销毁 webview,强制页面重新创建 webview。
|
|
除了销毁和重新创建 WebView 之外也可以通过修改 url 实现页面的强制刷新,比如 webview 的 url 第一次访问时 www.baidu.com?step=1
, 那么第二次访问我们将路由改成 www.baidu.com?step=2
也会让页面在下次出现的时机刷新。
两种方式都可行,UI渲染的开销上第一种方案会更大,第二种方案会弄脏 url。
分享
如果项目的每个页面都需要分享功能,那么我们可以通过在基类 basePage 里面注册一个默认的分享配置,在单独需要特殊分享的面去重写父类的 onShareAppMessage。
onShareAppMessage 是不能定义成异步的,通过设置 async 关键字会导致小程序无法识别该函数,所以这里面只能是同步代码。
scroll-view
在小程序里面如果想实现滚动条有两种方式:
- css 的 overflow: scroll;
- 小程序的原生组件:scroll-view;
第一种方式实测发现滚动性能差劲,会有明显的卡顿感,所以弃用;
第二种方式滚动的流畅度要高于第一种,但是会有莫名其妙的样式问题,如下图:
这里底部地区应该有明显的几十 rpx 的下坠,我们只能够通过用 margin 将页面顶起来。
渐变渲染
小程序里面使用 css 的 linear-gradient 一定要加 webkit 的前缀,否则渐变的样式将无效。
wepy
开发过程中也发现 wepy 框架存在不少问题,比如如果遇见 this.xxx = yyy 的赋值行为已经执行,但是页面 UI 并没有跟随状态同步,要执行 this.$apply()
来强制同步 UI 和状态。猜想这个问题应该是因为 wepy 的数据检查机制是依赖于脏检查实现的。
组件数据传递上如果期望子组件中的状态和父组件保持同步,需要添加 sync
关键字,否则会默认只下发一次,不会跟随变动而变动,如下:
|
|
这些问题在 wepy 的文档中都有提及,但是需要特别注意,不然很容易就不理解页面的 UI 状态为什么和预期差别那么大。
总结
以上就是开发过程中,我遇见的比较棘手的一些问题以及解决方案。在后续的迭代开发中其他问题也会在这里及时补充。
作者: Listen
来源: http://swarosky44.github.io
链接: http://swarosky44.github.io/2018/05/21/小程序 - 虚拟试衣项目总结/
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可